﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Collections;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;

namespace Vowei.Core.Models
{
    public enum WorkingTimeType
    {
        Working,
        Nonworking
    }

    [Serializable]
    public class PeriodTimePortion
    {
        public TimeSpan Duration 
        {
            get 
            {
                return Finish.Subtract(Start); 
            } 
        }

        public TimeSpan Start { get; set; }

        public TimeSpan Finish { get; set; }
    }

    [Serializable]
    public class Period
    {
        [Display(Name = "开始时间")]
        public DateTime Start { get; set; }

        private DateTime _finish;
        [Display(Name = "结束时间")]
        public DateTime Finish 
        {
            get { return _finish; }
            set
            {
                if (value <= Start)
                    throw new InvalidOperationException(string.Format("结束时间应该早于起始时间!"));

                _finish = value;
            }
        }

        [Display(Name = "时长")]
        public TimeSpan Duration { get { return Finish - Start; } }

        public Period Intersect(Period other)
        {
            if (other == null)
                return null;

            if (Start > other.Finish || other.Start > Finish)
                return null;
            else
            {
                return new Period
                {
                    Start = other.Start < Start ? Start : other.Start,
                    Finish = other.Finish < Finish ? other.Finish : Finish
                };
            }
        }
        
        public PeriodTimePortion Intersect(PeriodTimePortion other)
        {
            if (other == null)
                return null;
            
            // 因为Period的Finish属性不可能会小于Start,因此如果两个是属性相同,就可以判定是一天的开始和结束
            if ( (Start.TimeOfDay == Finish.TimeOfDay) && (other.Start != other.Finish) )
            {
                return new PeriodTimePortion
                {
                    Start = other.Start == TimeSpan.MinValue ? TimeSpan.Zero : other.Start,
                    Finish = other.Finish == TimeSpan.MaxValue ? TimeSpan.Zero : other.Finish
                };
            }
            else if ( (Start.TimeOfDay != Finish.TimeOfDay) && (other.Start == other.Finish) )
            {
                return new PeriodTimePortion
                {
                    Start = Start.TimeOfDay,
                    Finish = Finish.TimeOfDay
                };
            }
            else if (Start.TimeOfDay > other.Finish || other.Start > Finish.TimeOfDay)
                return null;
            else
            {
                return new PeriodTimePortion
                {
                    Start = other.Start < Start.TimeOfDay ? Start.TimeOfDay : other.Start,
                    Finish = other.Finish < Finish.TimeOfDay ? other.Finish : Finish.TimeOfDay
                };
            }
        }

        public bool Contains(DateTime date)
        {
            return date >= Start && date <= Finish;
        }

        public bool TimePortionContains(DateTime date)
        {
            return date.TimeOfDay >= Start.TimeOfDay && date.TimeOfDay <= Finish.TimeOfDay;
        }
    }

    class PeriodSetEqualityComparer : IComparer<Period>
    {
        public int Compare(Period x, Period y)
        {
            if (x == null && y == null)
                return 0;
            else if (x != null && y == null)
                return 1;
            else if (x == null && y != null)
                return -1;
            else if (x.Start > y.Finish)
                return 1;
            else if (y.Start > x.Finish)
                return -1;
            else // x和y相互完全包含或者有重叠时期的情况
                return 0;
        }
    }

    [Serializable]
    public abstract class ResourceCalendar : ITable, ISerializable, ICalendarException
    {
        protected ResourceCalendar()
        {
            ID = Guid.NewGuid();
            Exceptions = new List<ExceptionPeriod>();
            WorkWeeks = new SortedSet<WorkWeek>(new WorkWeekSetEqualityComparer());
        }

        public ResourceCalendar(string name) : this(name, CultureInfo.CurrentUICulture)
        {
        }

        public ResourceCalendar(string name, CultureInfo culture) : this()
        {
            Name = name;
            Culture = culture;
        }

        protected ResourceCalendar(SerializationInfo info, StreamingContext context)
        {
            Name = info.GetString("Name");
            Culture = (CultureInfo)info.GetValue("Culture", typeof(CultureInfo));
            WorkWeeks = (SortedSet<WorkWeek>)info.GetValue("WorkWeeks", typeof(SortedSet<WorkWeek>));
            Exceptions = (List<ExceptionPeriod>)info.GetValue("Exceptions", typeof(List<ExceptionPeriod>));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Name", Name);
            info.AddValue("Culture", Culture);
            info.AddValue("WorkWeeks", WorkWeeks);
            info.AddValue("Exceptions", Exceptions);
        }

        [Serializable]
        protected class WorkWeekSetEqualityComparer : IComparer<WorkWeek>
        {
            public int Compare(WorkWeek x, WorkWeek y)
            {
                var psc = new PeriodSetEqualityComparer();
                if (x == null && y == null)
                    return 0;
                else if (x != null && y == null)
                    return 1;
                else if (x == null && y != null)
                    return -1;
                else
                    return psc.Compare(x.EffectPeriod, y.EffectPeriod);
            }
        }

        public CultureInfo Culture { get; set; }

        public string Name { get; set; }

        public SortedSet<WorkWeek> WorkWeeks { get; set; }

        public List<ExceptionPeriod> Exceptions { get; protected set; }
        
        public Guid ID
        {
            get;
            set;
        }
        
        public ResourceCalendar Except(ExceptionPeriod period)
        {
            Exceptions.Add(period);

            return this;
        }

        ICalendarException ICalendarException.Except(ExceptionPeriod period)
        {
            return Except(period);
        }
    }

    [Serializable]
    public class WorkWeek
    {
        public WorkWeek(string name) 
        {
            Name = name;
            Days = new SortedSet<WorkDay>();
        }

        public string Name { get; private set; }

        public Period EffectPeriod { get; set; }

        public SortedSet<WorkDay> Days { get; private set; }
    }

    [Serializable]
    public class WorkDay : IComparable, IComparable<WorkDay>
    {
        public WorkDay(DayOfWeek day)
        {
            DayOfWeek = day;
            Periods = new List<Period>();
        }

        public DayOfWeek DayOfWeek { get; private set; }

        public WorkingTimeType Type { get; set; }

        public List<Period> Periods { get; private set; }
        
        public int CompareTo(WorkDay other)
        {
            if (other == null)
                return 1;
            else
                return DayOfWeek - other.DayOfWeek;
        }

        public int CompareTo(object obj)
        {
            return CompareTo(obj as WorkDay);
        }
    }
}
